(function (root, factory, undef) {
    if (typeof exports === 'object') {
        // CommonJS
        module.exports = exports = factory(require('./core'), require('./x64-core'));
    } else {
        if (typeof define === 'function' && define.amd) {
            // AMD
            define(['./core', './x64-core'], factory);
        } else {
            // Global (browser)
            factory(root.CryptoJS);
        }
    }
})(this, function (CryptoJS) {
    (function (Math) {
        // Shortcuts
        var C = CryptoJS;
        var C_lib = C.lib;
        var WordArray = C_lib.WordArray;
        var Hasher = C_lib.Hasher;
        var C_x64 = C.x64;
        var X64Word = C_x64.Word;
        var C_algo = C.algo; // Constants tables

        var RHO_OFFSETS = [];
        var PI_INDEXES = [];
        var ROUND_CONSTANTS = []; // Compute Constants

        (function () {
            var x = 1;
            var y = 0;
            // Compute rho offset constants
            for (var t = 0; t < 24; t++) {
                RHO_OFFSETS[x + 5 * y] = (((t + 1) * (t + 2)) / 2) % 64;
                var newX = y % 5;
                var newY = (2 * x + 3 * y) % 5;
                x = newX;
                y = newY;
            } // Compute pi index constants

            for (var x = 0; x < 5; x++) {
                for (var y = 0; y < 5; y++) {
                    PI_INDEXES[x + 5 * y] = y + ((2 * x + 3 * y) % 5) * 5;
                }
            } // Compute round constants

            var LFSR = 1;

            for (var i = 0; i < 24; i++) {
                var roundConstantMsw = 0;
                var roundConstantLsw = 0;

                for (var j = 0; j < 7; j++) {
                    if (LFSR & 1) {
                        var bitPosition = (1 << j) - 1;

                        if (bitPosition < 32) {
                            roundConstantLsw ^= 1 << bitPosition;
                        } else {
                        /* if (bitPosition >= 32) */
                            roundConstantMsw ^= 1 << (bitPosition - 32);
                        }
                    } // Compute next LFSR

                    if (LFSR & 128) {
                        // Primitive polynomial over GF(2): x^8 + x^6 + x^5 + x^4 + 1
                        LFSR = (LFSR << 1) ^ 113;
                    } else {
                        LFSR <<= 1;
                    }
                }

                ROUND_CONSTANTS[i] = X64Word.create(roundConstantMsw, roundConstantLsw);
            }
        })(); // Reusable objects for temporary values

        var T = [];

        (function () {
            for (var i = 0; i < 25; i++) {
                T[i] = X64Word.create();
            }
        })();
        /**
         * SHA-3 hash algorithm.
         */

        var SHA3 = (C_algo.SHA3 = Hasher.extend({
            /**
             * Configuration options.
             *
             * @property {number} outputLength
             *   The desired number of bits in the output hash.
             *   Only values permitted are: 224, 256, 384, 512.
             *   Default: 512
             */
            cfg: Hasher.cfg.extend({
                outputLength: 512
            }),
            _doReset: function () {
                var state = (this._state = []);

                for (var i = 0; i < 25; i++) {
                    state[i] = new X64Word.init();
                }

                this.blockSize = (1600 - 2 * this.cfg.outputLength) / 32;
            },
            _doProcessBlock: function (M, offset) {
                // Shortcuts
                var state = this._state;
                var nBlockSizeLanes = this.blockSize / 2; // Absorb

                for (var i = 0; i < nBlockSizeLanes; i++) {
                    // Shortcuts
                    var M2i = M[offset + 2 * i];
                    var M2i1 = M[offset + 2 * i + 1]; // Swap endian

                    M2i = (((M2i << 8) | (M2i >>> 24)) & 16711935) | (((M2i << 24) | (M2i >>> 8)) & 4278255360);
                    M2i1 = (((M2i1 << 8) | (M2i1 >>> 24)) & 16711935) | (((M2i1 << 24) | (M2i1 >>> 8)) & 4278255360); // Absorb message into state

                    var lane = state[i];
                    lane.high ^= M2i1;
                    lane.low ^= M2i;
                } // Rounds

                for (var round = 0; round < 24; round++) {
                    // Theta
                    for (var x = 0; x < 5; x++) {
                        var tMsw = 0;
                        var tLsw = 0;
                        // Mix column lanes
                        for (var y = 0; y < 5; y++) {
                            var lane = state[x + 5 * y];
                            tMsw ^= lane.high;
                            tLsw ^= lane.low;
                        } // Temporary values

                        var Tx = T[x];
                        Tx.high = tMsw;
                        Tx.low = tLsw;
                    }

                    for (var x = 0; x < 5; x++) {
                        // Shortcuts
                        var Tx4 = T[(x + 4) % 5];
                        var Tx1 = T[(x + 1) % 5];
                        var Tx1Msw = Tx1.high;
                        var Tx1Lsw = Tx1.low; // Mix surrounding columns

                        var tMsw = Tx4.high ^ ((Tx1Msw << 1) | (Tx1Lsw >>> 31));
                        var tLsw = Tx4.low ^ ((Tx1Lsw << 1) | (Tx1Msw >>> 31));

                        for (var y = 0; y < 5; y++) {
                            var lane = state[x + 5 * y];
                            lane.high ^= tMsw;
                            lane.low ^= tLsw;
                        }
                    } // Rho Pi

                    for (var laneIndex = 1; laneIndex < 25; laneIndex++) {
                        var tMsw;
                        var tLsw; // Shortcuts

                        var lane = state[laneIndex];
                        var laneMsw = lane.high;
                        var laneLsw = lane.low;
                        var rhoOffset = RHO_OFFSETS[laneIndex]; // Rotate lanes

                        if (rhoOffset < 32) {
                            tMsw = (laneMsw << rhoOffset) | (laneLsw >>> (32 - rhoOffset));
                            tLsw = (laneLsw << rhoOffset) | (laneMsw >>> (32 - rhoOffset));
                        } else {
                        /* if (rhoOffset >= 32) */
                            tMsw = (laneLsw << (rhoOffset - 32)) | (laneMsw >>> (64 - rhoOffset));
                            tLsw = (laneMsw << (rhoOffset - 32)) | (laneLsw >>> (64 - rhoOffset));
                        } // Transpose lanes

                        var TPiLane = T[PI_INDEXES[laneIndex]];
                        TPiLane.high = tMsw;
                        TPiLane.low = tLsw;
                    } // Rho pi at x = y = 0

                    var T0 = T[0];
                    var state0 = state[0];
                    T0.high = state0.high;
                    T0.low = state0.low; // Chi

                    for (var x = 0; x < 5; x++) {
                        for (var y = 0; y < 5; y++) {
                            // Shortcuts
                            var laneIndex = x + 5 * y;
                            var lane = state[laneIndex];
                            var TLane = T[laneIndex];
                            var Tx1Lane = T[((x + 1) % 5) + 5 * y];
                            var Tx2Lane = T[((x + 2) % 5) + 5 * y]; // Mix rows

                            lane.high = TLane.high ^ (~Tx1Lane.high & Tx2Lane.high);
                            lane.low = TLane.low ^ (~Tx1Lane.low & Tx2Lane.low);
                        }
                    } // Iota

                    var lane = state[0];
                    var roundConstant = ROUND_CONSTANTS[round];
                    lane.high ^= roundConstant.high;
                    lane.low ^= roundConstant.low;
                }
            },
            _doFinalize: function () {
                // Shortcuts
                var data = this._data;
                var dataWords = data.words;
                var nBitsTotal = this._nDataBytes * 8;
                var nBitsLeft = data.sigBytes * 8;
                var blockSizeBits = this.blockSize * 32; // Add padding

                dataWords[nBitsLeft >>> 5] |= 1 << (24 - (nBitsLeft % 32));
                dataWords[((Math.ceil((nBitsLeft + 1) / blockSizeBits) * blockSizeBits) >>> 5) - 1] |= 128;
                data.sigBytes = dataWords.length * 4; // Hash final blocks

                this._process(); // Shortcuts

                var state = this._state;
                var outputLengthBytes = this.cfg.outputLength / 8;
                var outputLengthLanes = outputLengthBytes / 8; // Squeeze

                var hashWords = [];

                for (var i = 0; i < outputLengthLanes; i++) {
                    // Shortcuts
                    var lane = state[i];
                    var laneMsw = lane.high;
                    var laneLsw = lane.low; // Swap endian

                    laneMsw = (((laneMsw << 8) | (laneMsw >>> 24)) & 16711935) | (((laneMsw << 24) | (laneMsw >>> 8)) & 4278255360);
                    laneLsw = (((laneLsw << 8) | (laneLsw >>> 24)) & 16711935) | (((laneLsw << 24) | (laneLsw >>> 8)) & 4278255360); // Squeeze state to retrieve hash

                    hashWords.push(laneLsw);
                    hashWords.push(laneMsw);
                } // Return final computed hash

                return new WordArray.init(hashWords, outputLengthBytes);
            },
            clone: function () {
                var clone = Hasher.clone.call(this);

                var state = (clone._state = this._state.slice(0));

                for (var i = 0; i < 25; i++) {
                    state[i] = state[i].clone();
                }

                return clone;
            }
        }));
        /**
         * Shortcut function to the hasher's object interface.
         *
         * @param {WordArray|string} message The message to hash.
         *
         * @return {WordArray} The hash.
         *
         * @static
         *
         * @example
         *
         *     var hash = CryptoJS.SHA3('message');
         *     var hash = CryptoJS.SHA3(wordArray);
         */

        C.SHA3 = Hasher._createHelper(SHA3);
        /**
         * Shortcut function to the HMAC's object interface.
         *
         * @param {WordArray|string} message The message to hash.
         * @param {WordArray|string} key The secret key.
         *
         * @return {WordArray} The HMAC.
         *
         * @static
         *
         * @example
         *
         *     var hmac = CryptoJS.HmacSHA3(message, key);
         */

        C.HmacSHA3 = Hasher._createHmacHelper(SHA3);
    })(Math);

    return CryptoJS.SHA3;
});
